<?php

namespace handle\message\lib\driver\lib;

use Exception;
use handle\message\lib\factory\SingletonFactory;
use handle\message\lib\manage\lib\ChessBoard;
use handle\message\lib\manage\lib\Player;
use handle\message\lib\manage\lib\Room;

class Event
{
    /**
     * 房间ID
     * @var string
     */
    private string $room_id;

    /**
     * 房间类
     * @var Room
     */
    private Room $room;

    /**
     * 玩家类 array[key] => Player
     * @var array
     */
    private array $players;

    /**
     * 棋盘类
     * @var ChessBoard
     */
    private ChessBoard $chessBoard;

    public function __construct(string $room_id){
        $this->room_id = $room_id;
    }

    /**
     * 获取房间类
     * @param string $room_id
     * @return void
     * @throws Exception
     */
    public function createRoom(string $room_id){
        $this->room = SingletonFactory::getInstance()->makeInstances('room')->get($room_id);
    }

    /**
     * 获取房间内的双方玩家类
     * @param string $room_id
     * @return void
     * @throws Exception
     */
    public function createPlayers(string $room_id){
        $this->players = SingletonFactory::getInstance()->makeInstances('player')->get($room_id);
    }

    /**
     * 获取棋盘类
     * @param string $room_id
     * @return void
     * @throws Exception
     */
    public function createChessBoard(string $room_id){
        $this->chessBoard = SingletonFactory::getInstance()->makeInstances('board')->get($room_id);
    }

    /**
     * 断线重连
     * @param int $uid
     * @return void
     * @throws Exception
     */
    public function reconnect(int $uid){
        if(count($this->players) == 2){
            $myPlayer = $this->getMyPlayer($uid);
            $data = [
                'myInfo' => $myPlayer->getMyCanSendData(),
                'oppositeInfo' => $this->getOppositePlayer($uid)->getMyCanSendData(),
                'roomStatus' => $this->room->getStatus(),
                'turn' => $this->getMyPlayer($uid)->isMyTurn(),
            ];
            if(!empty($this->chessBoard)){
                $data['board'] = $this->chessBoard->getBoard();
                $data['round'] = $this->chessBoard->getRound();
            }
            $myPlayer->getConnection()->sendData('reconnect', $data);
            $this->sendUnselectedRequestEvents($data);
        }
    }


    /**
     * 发送出未选择的事件，比如：悔棋请求 or 求和请求
     * @return void
     */
    private function sendUnselectedRequestEvents(array $data){
        if($data['oppositeInfo']['sendWithdraw'] === 2 or $data['oppositeInfo']['sendGameTie'] === 2){
            $type = $data['oppositeInfo']['sendWithdraw'] === 2 ? 1 : 2;
            list($result, $title, $mode, $failMsg, $successMsg) = $this->getMyPlayer($data['oppositeInfo']['uid'])->getSendRequest($type);
            $this->sendRequest($data['oppositeInfo']['uid'], $type, $title, $mode, $successMsg);
        }
    }


    /**
     * 开始游戏
     * @return void
     * @throws Exception
     */
    public function startGame(int $uid){
        if($this->room->getStatus() === 1 and $this->getMyPlayer($uid)->isHomeowner()){
            //当游戏开始时，先手设置为 谁是白棋谁先手
            $this->getMyPlayer($uid)->updateTurn($this->getMyPlayer($uid)->getMyCanSendData('chess') === 1);    //设置我的回合
            $this->getOppositePlayer($uid)->updateTurn($this->getOppositePlayer($uid)->getMyCanSendData('chess') === 1); //设置对方回合

            $this->getMyPlayer($uid)->getConnection()->sendMsg('去取得胜利吧', 'success');
            $this->getMyPlayer($uid)->getConnection()->sendData('startGame');
            $this->getOppositePlayer($uid)->getConnection()->sendMsg('去取得胜利吧', 'success');
            $this->getOppositePlayer($uid)->getConnection()->sendData('startGame');
        }
    }

    /**
     * 某个人进入房间
     * @param int $uid
     * @return void
     * @throws Exception
     */
    public function enterRoom(int $uid){
        if(count($this->players) == 2){ //有两个人，将通知对方我加入了房间，并将我的数据发送给对方，将对方的数据发送给我
            $myPlayer = $this->getMyPlayer($uid);
            $oppositePlayer = $this->getOppositePlayer($uid);
            $this->room->checkMyselfInRoom($uid) or $oppositePlayer->getConnection()->sendMsg('有玩家加入了房间');
            $this->sendUsersInfos($oppositePlayer, $myPlayer);
            $this->sendUsersInfos($myPlayer, $oppositePlayer);
        }
    }

    /**
     * 将信息发送至对方
     * @param Player $myPlayer
     * @param Player $oppositePlayer
     * @return void
     */
    public function sendUsersInfos(Player $myPlayer, Player $oppositePlayer){
        $myPlayer->getConnection()->sendData('usersInfos', [
            'info' => $oppositePlayer->getMyCanSendData()
        ]);
        $this->sendBoardInfo($myPlayer);
    }

    /**
     * 发送棋盘信息
     * @param Player $player
     * @return void
     */
    public function sendBoardInfo(Player $player){
        $this->room->getStatus() === 1 and $player->getConnection()->sendData('board', [
            'info' => $this->chessBoard->getBoard(),
        ]);
    }

    /**
     * 退出房间
     * @param int $uid
     * @param int $closeType
     * @return void
     * @throws Exception
     */
    public function exitRoom(int $uid, int $closeType = 0){
        switch (!empty($closeType) ? $closeType : $this->getExitRoomProgramme($uid)){
            case 1: //通知对方消息，关闭我的连接----玩家退出房间
                $oppositePlayer = $this->getOppositePlayer($uid);
                $oppositePlayer->getConnection()->sendMsg('对方退出了房间');
                $oppositePlayer->getConnection()->sendData('leaveRoom');
                $this->getMyPlayer($uid)->getConnection()->close();
                SingletonFactory::getInstance()->makeInstances('player')->del($this->room_id, $uid);
                if($this->room->getStatus() === 1){ //在游戏中，玩家退出了房间，通知房主胜利
                    $this->room->setOutcome($this->getOppositePlayer($uid)->getMyCanSendData('chess'));
                    $this->sendCompetitionResults($uid, 4);
                }
                $this->createPlayers($this->room_id);
                break;
            case 2: //通知对方消息，双方都断开连接------房主退出了房间
                $oppositePlayer = $this->getOppositePlayer($uid);
                $oppositePlayer->getConnection()->sendMsg('房主解散了房间');
                $oppositePlayer->getConnection()->sendData('closeRoom');
                if($this->room->getStatus() === 1){ //在游戏中，房主解散了房间，通知玩家胜利
                    $this->room->setOutcome($this->getOppositePlayer($uid)->getMyCanSendData('chess'));
                    $this->sendCompetitionResults($uid, 4);
                }
                $oppositePlayer->getConnection()->close();
                $this->getMyPlayer($uid)->getConnection()->close();
                $this->notifyOperableManageDelete();    //删除房间信息
                break;
            case 3: //只有房主，不需要通知
                $this->getMyPlayer($uid)->getConnection()->close();
                $this->notifyOperableManageDelete();    //删除房间信息
                break;
            case 1006: //通知双方，系统关闭了房间
                if(count($this->players) != 0) {
                    if (count($this->players) === 2) {
                        $this->getOppositePlayer($uid)->getConnection()->sendMsg('系统检测数据异常，关闭房间');
                        $this->getOppositePlayer($uid)->getConnection()->sendData('closeRoom');
                        $this->getOppositePlayer($uid)->getConnection()->close();
                    }
                    $this->getMyPlayer($uid)->getConnection()->sendMsg('系统检测数据异常，关闭房间');
                    $this->getMyPlayer($uid)->getConnection()->sendData('closeRoom');
                    $this->getMyPlayer($uid)->getConnection()->close();
                }
                $this->notifyOperableManageDelete();    //删除房间信息
                break;
        }
    }

    /**
     * 获取退出房间的情况
     * @param int $uid
     * @return int 1: 仅通知(只是玩家退出了房间，房间内有两个人), 2: 通知并且解散(房间内两个人，房主退出了游戏) 3: 解散房间(只有房主在房间内)
     */
    public function getExitRoomProgramme(int $uid): int
    {
        return count($this->players) === 2 ? ($this->getMyPlayer($uid)->isHomeowner() ? 2 : 1) : 3;
    }

    /**
     * 可操作的管理器
     * @var array|string[]
     */
    private array $operableManage = ['room', 'event', 'board', 'player'];
    /**
     * 通知可操作的管理器删除房间，因为房间已经没有人了，直接解散删除房间
     * @return void
     */
    private function notifyOperableManageDelete(){
        foreach ($this->operableManage as $manage){
            SingletonFactory::getInstance()->makeInstances($manage)->del($this->room_id);
        }
    }

    /**
     * 发送相关请求
     * @param int $uid
     * @param int $type 1:悔棋请求, 2:和棋请求
     * @return void
     * @throws Exception
     */
    public function sendRelatedRequest(int $uid, int $type){
        $myPlayer = $this->getMyPlayer($uid);
        if ($type == 1 and $myPlayer->isMyTurn()) {
            $myPlayer->getConnection()->sendMsg('不是对方回合，无法悔棋');
            return;
        }
        list($result, $title, $mode, $failMsg, $successMsg) = $myPlayer->getSendRequest($type);
        if($result > 1){
            $myPlayer->getConnection()->sendMsg($failMsg); // 已经没有机会发送悔棋请求了
            $myPlayer->getConnection()->sendData($type === 1 ? 'withdrawNotice' : 'gameTieNotice');
            return;
        }
        $this->sendRequest($uid, $type, $title, $mode, $successMsg);    //发送请求消息
        $this->openRequestTimer($uid, $type);  //启动请求计时器，对方未做出选择-时间到自动拒绝
    }

    /**
     * 发送悔棋、求和请求
     * @param $uid
     * @param $type
     * @param $title
     * @param $mode
     * @param $successMsg
     * @return void
     * @throws Exception
     */
    private function sendRequest($uid, $type, $title, $mode, $successMsg){
        $this->getOppositePlayer($uid)->getConnection()->sendMsg($successMsg, 'warning', [
            'messageBoxType' => 'confirm',
            'title' => $title . '请求',
            'type' => $mode,
        ]); //发送悔棋/和棋 请求给对面
        $this->getMyPlayer($uid)->updateSendRequest($type, 2);
        $this->getMyPlayer($uid)->getConnection()->sendMsg('等待对方同意', 'success'); //发送成功提示给自己
    }

    /**
     * 启动请求计时器
     * @param $uid
     * @param $type
     * @return void
     * @throws Exception
     */
    private function openRequestTimer($uid, $type){
        \auxiliary\timer\TimerGroup::getInstance()->bind($this->room_id . '-' . $uid . '-request-timer', 60, function ($oppositePlayer, $type){    //启动定时器，如果对方60s内没有做出选择，自动取消请求，通知双方
            //先发送请求更新状态，再发送消息通知，顺序不能反，不然看不见消息
            $oppositePlayer->getConnection()->sendData($type === 1 ? 'withdrawNotice' : 'gameTieNotice');   //通知对方
            $oppositePlayer->getConnection()->sendMsg('长时间未做出选择，系统自动拒绝了'); // 通知对方，系统自动取消请求
        }, [$this->getOppositePlayer($uid), $type], false);
    }

    /**
     * 发送拒绝通知
     * @param int $uid
     * @param int $type 1:悔棋拒绝, 2:和棋拒绝
     * @return void
     * @throws Exception
     */
    public function sendRefuseNotify(int $uid, int $type){
        \auxiliary\timer\TimerGroup::getInstance()->unbind($this->room_id . '-' . $this->getOppositePlayer($uid)->getUid() . '-request-timer'); //删除定时器任务
        $this->getOppositePlayer($uid)->updateSendRequest($type, 3);
        //先发送请求更新状态，再发送消息通知，顺序不能反，不然看不见消息
        $this->getOppositePlayer($uid)->getConnection()->sendData($type === 1 ? 'withdrawNotice' : 'gameTieNotice');
        $this->getOppositePlayer($uid)->getConnection()->sendMsg('您的请求被拒绝了');
    }

    /**
     * 发送棋局结果
     * @param int $uid
     * @param int $outcome
     * @return void
     * @throws Exception
     */
    public function sendCompetitionResults(int $uid, int $outcome = 0){
        switch (empty($outcome) ? $this->room->getOutcome() : $outcome){
            case 1: //白棋胜利
                list($whitePlayer, $blackPlayer) = $this->getMyPlayer($uid)->getMyCanSendData('chess') === 1 ? [$this->getMyPlayer($uid), $this->getOppositePlayer($uid)] : [$this->getOppositePlayer($uid), $this->getMyPlayer($uid)];
                $whitePlayer->getConnection()->sendData('endGame');
                $whitePlayer->getConnection()->sendMsg('你赢了', 'success', [
                    'messageBoxType' => 'box',
                    'title' => '棋局结果',
                ]);
                $blackPlayer->getConnection()->sendData('endGame');
                $blackPlayer->getConnection()->sendMsg('你输了', 'error', [
                    'messageBoxType' => 'box',
                    'title' => '棋局结果',
                ]);
                break;
            case 2: //黑棋胜利
                list($whitePlayer, $blackPlayer) = $this->getMyPlayer($uid)->getMyCanSendData('chess') === 1 ? [$this->getMyPlayer($uid), $this->getOppositePlayer($uid)] : [$this->getOppositePlayer($uid), $this->getMyPlayer($uid)];
                $whitePlayer->getConnection()->sendData('endGame');
                $whitePlayer->getConnection()->sendMsg('你输了', 'error', [
                    'messageBoxType' => 'box',
                    'title' => '棋局结果',
                ]);
                $blackPlayer->getConnection()->sendData('endGame');
                $blackPlayer->getConnection()->sendMsg('你赢了', 'success', [
                    'messageBoxType' => 'box',
                    'title' => '棋局结果',
                ]);
                break;
            case 3: //平局
                \auxiliary\timer\TimerGroup::getInstance()->unbind($this->room_id . '-' . $this->getOppositePlayer($uid)->getUid() . '-request-timer'); //删除定时器任务
                $this->getOppositePlayer($uid)->getConnection()->sendData('gameTieNotice');
                foreach ($this->players as $player){
                    $player->getConnection()->sendData('endGame');
                    $player->getConnection()->sendMsg('平局', 'warning', [
                        'messageBoxType' => 'box',
                        'title' => '棋局结果',
                    ]);
                }
                break;
            case 1006: //系统级-平局
                foreach ($this->players as $player){
                    $player->getConnection()->sendData('endGame');
                    $player->getConnection()->sendMsg('系统检测数据异常-棋局结果平局', 'warning', [
                        'messageBoxType' => 'box',
                        'title' => '棋局结果',
                    ]);
                }
                break;
            case 4: //对方逃跑了
                $this->getOppositePlayer($uid)->getConnection()->sendData('endGame');
                $this->getOppositePlayer($uid)->getConnection()->sendMsg('对方逃跑了，你赢了', 'warning', [
                    'messageBoxType' => 'box',
                    'title' => '棋局结果',
                ]);//通知对方我逃跑了
                break;
            case 5: //认输，谁发起的请求谁认输
                $this->room->setOutcome($this->getOppositePlayer($uid)->getMyCanSendData('chess'));
                $this->getOppositePlayer($uid)->getConnection()->sendData('endGame');
                $this->getOppositePlayer($uid)->getConnection()->sendMsg('对方认输了，你赢了', 'warning', [
                    'messageBoxType' => 'box',
                    'title' => '棋局结果',
                ]);//通知对方我认输了
                $this->getMyPlayer($uid)->getConnection()->sendData('endGame');
                $this->getMyPlayer($uid)->getConnection()->sendMsg('你输了', 'warning', [
                    'messageBoxType' => 'box',
                    'title' => '棋局结果',
                ]);//通知我，我输了
                break;
        }
        $this->resetRPB();  //重置房间类、双方玩家类、棋盘类(Room、Player、Board)
    }

    /**
     * 棋子位置被占用
     * @param int $uid
     * @return void
     */
    public function positionHasBeenOccupied(int $uid){
        $this->getMyPlayer($uid)->getConnection()->sendMsg('该位置已经有棋子了');
    }

    /**
     * 发送我的棋子位置
     * @return void
     * @throws Exception
     */
    public function seedMyChessPosition(){
        $lastOne = $this->chessBoard->getLastOneHistory();
        $this->updatePlayerTurn($lastOne['uid']);
        $this->getOppositePlayer($lastOne['uid'])->getConnection()->sendData('board', ['info' => $this->chessBoard->getBoard(), 'chess' => $lastOne['color']]);
        if($this->isWin($this->chessBoard->getBoard(), [$lastOne['x'], $lastOne['y']], $lastOne['color'])){
            $this->room->setOutcome($lastOne['color']);
            $this->sendCompetitionResults($lastOne['uid']);
        }
    }

    /**
     * 更新玩家回合
     * @param int $uid
     * @return void
     * @throws Exception
     */
    public function updatePlayerTurn(int $uid){
        $this->getMyPlayer($uid)->updateTurn();
        $this->getOppositePlayer($uid)->updateTurn();
    }

    /**
     * 是否落子定胜负
     * @param array $board 整局棋盘
     * @param array $coordinate 落子坐标
     * @param int $chessColor 落子颜色
     * @return bool
     */
    public function isWin(array $board, array $coordinate, int $chessColor): bool
    {
        return \handle\message\lib\manage\strategy\ChessBoard::getInstance()->checkGameOver($board, $coordinate, $chessColor);
    }

    /**
     * 悔棋操作，将上一步的棋盘恢复
     * @return void
     * @throws Exception
     */
    public function withdraw(){
        \auxiliary\timer\TimerGroup::getInstance()->unbind($this->room_id . '-' . $this->chessBoard->getLastOneHistory('uid') . '-request-timer'); //删除定时器任务
        //此时是玩家A悔棋，玩家B同意，进入这段代码的是玩家B，要拿到对方的Player直接取棋盘历史记录里的最后一条记录就能拿到对方的uid，从而拿到对方的Player
        $this->updatePlayerTurn($this->chessBoard->getLastOneHistory('uid'));
        $this->getMyPlayer($this->chessBoard->getLastOneHistory('uid'))->getConnection()->sendData('withdrawNotice');
        $this->getMyPlayer($this->chessBoard->getLastOneHistory('uid'))->updateSendRequest(1, 3);//将对方的Player玩家类的悔棋状态更新为3
        foreach ($this->players as $player){
            $player->getConnection()->sendData('withdraw', ['info' => $this->chessBoard->getBoard(), 'chess' => $this->chessBoard->getLastOneHistory('color')]);
        }
    }

    /**
     * 获取对战玩家Player
     * @param int $uid 玩家ID
     * @return Player
     * @throws Exception
     */
    public function getOppositePlayer(int $uid): Player
    {
        if(count($this->players) != 2) {
            throw new Exception(error('对战玩家数据异常664'));
        }
        $arr = $this->players;
        unset($arr[$uid]);
        return end($arr);
    }

    /**
     * 获取房主Uid
     * @return int
     */
    public function getHomeownerUid(): int
    {
        return $this->players[$this->room->getPlayerInfo()[0]]->isHomeowner() ? $this->players[$this->room->getPlayerInfo()[0]]->getUid() : $this->players[$this->room->getPlayerInfo()[1]]->getUid();
    }

    /**
     * 获取我的Player
     * @param int $uid 玩家ID
     * @return Player
     */
    public function getMyPlayer(int $uid): Player
    {
        return $this->players[$uid];
    }

    /**
     * 游戏结束时，重置房间、玩家和棋盘
     * @return void
     */
    private function resetRPB(){
        foreach($this->players as $player){
            $player->init();
        }
        $this->room->init();
        $this->chessBoard->init();
    }

    /**
     * 系统关闭房间
     * @return void
     * @throws Exception
     */
    public function systemCloseRoom()
    {
        $this->systemEndGame();
        $this->exitRoom($this->getHomeownerUid(), 1006);
        SingletonFactory::getInstance()->makeInstances('system')->getConnection()->sendMsg('socket反馈: 关闭成功');
    }

    /**
     * 系统关闭棋局
     * @return void
     * @throws Exception
     */
    public function systemEndGame()
    {
        //判断是否正在游戏中
        if($this->room->getStatus() == 1){
            //设置游戏平局，结束游戏
            $this->room->setOutcome(3);
            $this->sendCompetitionResults($this->getHomeownerUid(), 1006);
            SingletonFactory::getInstance()->makeInstances('system')->getConnection()->sendMsg('socket反馈: 系统关闭棋局');
        }
        SingletonFactory::getInstance()->makeInstances('system')->getConnection()->sendMsg('socket反馈: 棋局还在准备中...');
    }

}